/***
    ************************************************************************************************************************************************************************************************
>>>>> 重要说明：
    *
    *  1.屏幕配置为16位RGB565格式
    *  2.SPI通信速度为 18M  (如果觉得刷屏速度过慢，可以修改 LCD_SPI_Config() SPI的时钟 )
    *********************************************************************************************************************************************************************************************极客KE*****
***/

#include "lcd.h"

#include "ascii.h"

SPI_HandleTypeDef hspi1; // SPI_HandleTypeDef 结构体变量

#define LCD_SPI hspi1 // SPI局部宏，方便修改和移植

static struct // LCD相关参数结构体
{
    uint16_t Direction; //	显示方向
    uint16_t BackColor; //	背景颜色
    uint16_t Width;     // 屏幕像素长度
    uint16_t Height;    // 屏幕像素宽度
} LCD;

uint16_t *LCD_BUFF = NULL;
/*************************************************************************************************
 *	函 数 名:	HAL_SPI_MspInit
 *	入口参数:	hspi - SPI_HandleTypeDef定义的变量，即表示定义的 SPI 句柄
 *	返 回 值:	无
 *	函数功能:	初始化 SPI1 引脚
 *	说    明:	无
 *************************************************************************************************/

void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitTypeDef GPIO_InitStruct1 = {0};
    if (hspi->Instance == SPI1)
    {
        LCD_SPI1_CLK_ENABLE; // 使能SPI1时钟

        GPIO_LCD_SCK_CLK;       // 使能 SCK          引脚时钟
        GPIO_LCD_SDA_CLK;       // 使能 SDA          引脚时钟
        GPIO_LCD_CS_CLK;        // 使能 CS           引脚时钟
        GPIO_LCD_Backlight_CLK; // 使能 背光         引脚时钟
        GPIO_LCD_DC_CLK;        // 使能 数据指令选择 引脚时钟

        // 初始化 SCK、MOSI
        GPIO_InitStruct.Pin = LCD_SCK_PIN;                 // SCK引脚
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;            // 复用推挽输出
        GPIO_InitStruct.Pull = GPIO_NOPULL;                // 无上下拉
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; // 最高速度等级
        GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;         // 复用功能为SPI3
        HAL_GPIO_Init(LCD_SCK_PORT, &GPIO_InitStruct);

        GPIO_InitStruct.Pin = LCD_SDA_PIN; // SCK、MOSI引脚
        HAL_GPIO_Init(LCD_SDA_PORT, &GPIO_InitStruct);

        // 初始化片选引脚

        GPIO_InitStruct1.Pin = LCD_CS_PIN | LCD_Backlight_PIN | LCD_DC_PIN; // 背光 引脚
        GPIO_InitStruct1.Mode = GPIO_MODE_OUTPUT_PP;                        // 推挽输出模式
        GPIO_InitStruct1.Pull = GPIO_NOPULL;                                // 无上下拉
        GPIO_InitStruct1.Speed = GPIO_SPEED_FREQ_HIGH;                      // 速度等级高
        HAL_GPIO_Init(LCD_CS_PORT, &GPIO_InitStruct1);                      // 初始化

        LCD_DC_Data;       // DC引脚拉高，默认处于写数据状态
        LCD_CS_H;          // 拉高片选，禁止通信
        LCD_Backlight_OFF; // 先关闭背光，初始化完成之后再打开
    }
}

/*************************************************************************************************
 *	函 数 名:	MX_SPI1_Init
 *	入口参数:	无
 *	返 回 值:	无
 *	函数功能:	初始化SPI配置
 *	说    明:	使用硬件片选
 *************************************************************************************************/

void MX_SPI1_Init(void)
{
    LCD_SPI.Instance = SPI1;                      //	使用SPI1
    LCD_SPI.Init.Mode = SPI_MODE_MASTER;          //	主机模式
    LCD_SPI.Init.Direction = SPI_DIRECTION_1LINE; //	单线
    LCD_SPI.Init.DataSize = SPI_DATASIZE_8BIT;    //	8位数据宽度
    LCD_SPI.Init.CLKPolarity = SPI_POLARITY_LOW;  //	CLK空闲时保持低电平
    LCD_SPI.Init.CLKPhase = SPI_PHASE_1EDGE;      //	数据在CLK第一个边沿有效
    LCD_SPI.Init.NSS = SPI_NSS_SOFT;              //	软件片选

    // SPI1 挂载在APB2总线，总线时钟84MHz
    // 设置为2分频，得到42MHz的SPI驱动时钟
    // SPI3 挂载在APB1总线，总线时钟42MHz
    // 设置为2分频，得到21MHz的SPI驱动时钟
    LCD_SPI.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2; // 2分频

    LCD_SPI.Init.FirstBit = SPI_FIRSTBIT_MSB;                 //	高位在先
    LCD_SPI.Init.TIMode = SPI_TIMODE_DISABLE;                 //	禁止TI模式
    LCD_SPI.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; //	禁止CRC
    LCD_SPI.Init.CRCPolynomial = 0x0;                         // CRC校验项，这里用不到

    HAL_SPI_Init(&LCD_SPI); // 初始化SPI

    // 为了传输效率，本例程的SPI通信采用寄存器操作，不用HAL库自带的收发函数，所以需要单独配置下面2个
    __HAL_SPI_ENABLE(&LCD_SPI); // 使能SPI
    SPI_1LINE_TX(&LCD_SPI);     // 单线发送模式
}

/*****************************************************************************************
 *	函 数 名: LCD_WriteCMD
 *	入口参数: CMD - 需要写入的控制指令
 *	返 回 值: 无
 *	函数功能: 用于写入控制字
 *	说    明: 无
 ******************************************************************************************/

void LCD_WriteCommand(uint8_t lcd_command)
{

    while ((LCD_SPI.Instance->SR & 0x0080) != RESET)
        ; // 先判断SPI是否空闲，等待通信完成

    LCD_DC_Command; //	DC引脚输出低，代表写指令

    (&LCD_SPI)->Instance->DR = lcd_command; // 发送数据
    while ((LCD_SPI.Instance->SR & 0x0002) == 0)
        ; // 等待发送缓冲区清空
    while ((LCD_SPI.Instance->SR & 0x0080) != RESET)
        ; //	等待通信完成

    LCD_DC_Data; //	DC引脚输出高，代表写数据
}

/****************************************************************************************************************************************
 *	函 数 名: LCD_WriteData_8bit
 *
 *	入口参数: lcd_data - 需要写入的数据，8位
 *
 *	函数功能: 写入8位数据
 *
 ****************************************************************************************************************************************/

void LCD_WriteData_8bit(uint8_t lcd_data)
{
    LCD_SPI.Instance->DR = lcd_data; // 发送数据
    while ((LCD_SPI.Instance->SR & 0x0002) == 0)
        ; // 等待发送缓冲区清空
}

/****************************************************************************************************************************************
 *	函 数 名: LCD_WriteData_16bit
 *
 *	入口参数: lcd_data - 需要写入的数据，16位
 *
 *	函数功能: 写入16位数据
 *
 ****************************************************************************************************************************************/

void LCD_WriteData_16bit(uint16_t lcd_data)
{
    LCD_SPI.Instance->DR = lcd_data >> 8; // 发送数据，高8位
    while ((LCD_SPI.Instance->SR & 0x0002) == 0)
        ;                            // 等待发送缓冲区清空
    LCD_SPI.Instance->DR = lcd_data; // 发送数据，低8位
    while ((LCD_SPI.Instance->SR & 0x0002) == 0)
        ; // 等待发送缓冲区清空
}

/****************************************************************************************************************************************
 *	函 数 名: LCD_WriteBuff
 *
 *	入口参数: DataBuff - 数据区，DataSize - 数据长度
 *
 *	函数功能: 批量写入数据到屏幕
 *
 ****************************************************************************************************************************************/

void LCD_WriteBuff(uint16_t *DataBuff, uint16_t DataSize)
{
    uint32_t i;

    LCD_SPI.Instance->CR1 &= 0xFFBF; // 关闭SPI
    LCD_SPI.Instance->CR1 |= 0x0800; // 切换成16位数据格式
    LCD_SPI.Instance->CR1 |= 0x0040; // 使能SPI

    LCD_CS_L; // 片选拉低，使能IC

    for (i = 0; i < DataSize; i++)
    {

        LCD_SPI.Instance->DR = DataBuff[i];
        while ((LCD_SPI.Instance->SR & 0x0002) == 0)
            ; // 等待发送缓冲区清空
    }
    while ((LCD_SPI.Instance->SR & 0x0080) != RESET)
        ;     //	等待通信完成
    LCD_CS_H; // 片选拉高

    LCD_SPI.Instance->CR1 &= 0xFFBF; // 关闭SPI
    LCD_SPI.Instance->CR1 &= 0xF7FF; // 切换成8位数据格式
    LCD_SPI.Instance->CR1 |= 0x0040; // 使能SPI
}

/****************************************************************************************************************************************
 *	函 数 名:	LCD_SetBackColor
 *
 *	入口参数:	Color - 要显示的颜色，示例：0x0000FF 表示蓝色
 *
 *	函数功能:	设置背景色,此函数用于清屏以及显示字符的背景色
 *
 *	说    明:	1. 为了方便用户使用自定义颜色，入口参数 Color 使用24位 RGB888的颜色格式，用户无需关心颜色格式的转换
 *					2. 24位的颜色中，从高位到低位分别对应 R、G、B  3个颜色通道
 *
 *****************************************************************************************************************************************/

void LCD_SetBackColor(uint32_t Color)
{
    uint16_t Red_Value = 0, Green_Value = 0, Blue_Value = 0; // 各个颜色通道的值

    Red_Value = (uint16_t)((Color & 0x00F80000) >> 8); // 转换成 16位 的RGB565颜色
    Green_Value = (uint16_t)((Color & 0x0000FC00) >> 5);
    Blue_Value = (uint16_t)((Color & 0x000000F8) >> 3);

    LCD.BackColor = (uint16_t)(Red_Value | Green_Value | Blue_Value); // 将颜色写入全局LCD参数
}

/****************************************************************************************************************************************
 *	函 数 名: lcd_init
 *
 *	函数功能: 初始化SPI以及屏幕控制器的各种参数
 *
 ****************************************************************************************************************************************/

void lcd_init(void)
{

    MX_SPI1_Init(); // 初始化SPI和控制引脚

    HAL_Delay(10); // 屏幕刚完成复位时（包括上电复位），需要等待5ms才能发送指令

    LCD_CS_L;                 // 片选拉低，使能IC，开始通信
    LCD_WriteCommand(0x36);   // 显存访问控制 指令，用于设置访问显存的方式
    LCD_WriteData_8bit(0x00); // 配置成 从上到下、从左到右，RGB像素格式

    LCD_WriteCommand(0x3A);   // 接口像素格式 指令，用于设置使用 12位、16位还是18位色
    LCD_WriteData_8bit(0x05); // 此处配置成 16位 像素格式

    // 接下来很多都是电压设置指令，直接使用厂家给设定值
    LCD_WriteCommand(0xB2);
    LCD_WriteData_8bit(0x0C);
    LCD_WriteData_8bit(0x0C);
    LCD_WriteData_8bit(0x00);
    LCD_WriteData_8bit(0x33);
    LCD_WriteData_8bit(0x33);

    LCD_WriteCommand(0xB7);   // 栅极电压设置指令
    LCD_WriteData_8bit(0x35); // VGH = 13.26V，VGL = -10.43V

    LCD_WriteCommand(0xBB);   // 公共电压设置指令
    LCD_WriteData_8bit(0x19); // VCOM = 1.35V

    LCD_WriteCommand(0xC0);
    LCD_WriteData_8bit(0x2C);

    LCD_WriteCommand(0xC2);   // VDV 和 VRH 来源设置
    LCD_WriteData_8bit(0x01); // VDV 和 VRH 由用户自由配置

    LCD_WriteCommand(0xC3);   // VRH电压 设置指令
    LCD_WriteData_8bit(0x12); // VRH电压 = 4.6+( vcom+vcom offset+vdv)

    LCD_WriteCommand(0xC4);   // VDV电压 设置指令
    LCD_WriteData_8bit(0x20); // VDV电压 = 0v

    LCD_WriteCommand(0xC6);   // 正常模式的帧率控制指令
    LCD_WriteData_8bit(0x0F); // 设置屏幕控制器的刷新帧率为60帧

    LCD_WriteCommand(0xD0);   // 电源控制指令
    LCD_WriteData_8bit(0xA4); // 无效数据，固定写入0xA4
    LCD_WriteData_8bit(0xA1); // AVDD = 6.8V ，AVDD = -4.8V ，VDS = 2.3V

    LCD_WriteCommand(0xE0); // 正极电压伽马值设定
    LCD_WriteData_8bit(0xD0);
    LCD_WriteData_8bit(0x04);
    LCD_WriteData_8bit(0x0D);
    LCD_WriteData_8bit(0x11);
    LCD_WriteData_8bit(0x13);
    LCD_WriteData_8bit(0x2B);
    LCD_WriteData_8bit(0x3F);
    LCD_WriteData_8bit(0x54);
    LCD_WriteData_8bit(0x4C);
    LCD_WriteData_8bit(0x18);
    LCD_WriteData_8bit(0x0D);
    LCD_WriteData_8bit(0x0B);
    LCD_WriteData_8bit(0x1F);
    LCD_WriteData_8bit(0x23);

    LCD_WriteCommand(0xE1); // 负极电压伽马值设定
    LCD_WriteData_8bit(0xD0);
    LCD_WriteData_8bit(0x04);
    LCD_WriteData_8bit(0x0C);
    LCD_WriteData_8bit(0x11);
    LCD_WriteData_8bit(0x13);
    LCD_WriteData_8bit(0x2C);
    LCD_WriteData_8bit(0x3F);
    LCD_WriteData_8bit(0x44);
    LCD_WriteData_8bit(0x51);
    LCD_WriteData_8bit(0x2F);
    LCD_WriteData_8bit(0x1F);
    LCD_WriteData_8bit(0x1F);
    LCD_WriteData_8bit(0x20);
    LCD_WriteData_8bit(0x23);

    LCD_WriteCommand(0x21); // 打开反显，因为面板是常黑型，操作需要反过来

    // 退出休眠指令，LCD控制器在刚上电、复位时，会自动进入休眠模式 ，因此操作屏幕之前，需要退出休眠
    LCD_WriteCommand(0x11); // 退出休眠 指令
    HAL_Delay(120);         // 需要等待120ms，让电源电压和时钟电路稳定下来

    // 打开显示指令，LCD控制器在刚上电、复位时，会自动关闭显示
    LCD_WriteCommand(0x29); // 打开显示
    while ((LCD_SPI.Instance->SR & 0x0080) != RESET)
        ;     //	等待通信完成
    LCD_CS_H; // 片选拉高
    // 以下进行一些驱动的默认设置
    LCD_SetDirection(Direction_V_Flip); //	设置显示方向
    LCD_SetBackColor(LCD_BLACK);        // 设置背景色
    LCD_Clear();                        // 清屏
    // 全部设置完毕之后，打开背光
    LCD_Backlight_ON; // 引脚输出高电平点亮背光
}

/****************************************************************************************************************************************
 *	函 数 名:	 LCD_SetAddress
 *
 *	入口参数:	 x1 - 起始水平坐标   y1 - 起始垂直坐标
 *              x2 - 终点水平坐标   y2 - 终点垂直坐标
 *
 *	函数功能:   设置需要显示的坐标区域
 *****************************************************************************************************************************************/

void LCD_SetAddress(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
{
    LCD_CS_L; // 片选拉低，使能IC

    LCD_WriteCommand(0x2a); //	列地址设置，即X坐标
    LCD_WriteData_16bit(x1);
    LCD_WriteData_16bit(x2);

    LCD_WriteCommand(0x2b); //	行地址设置，即Y坐标
    LCD_WriteData_16bit(y1);
    LCD_WriteData_16bit(y2);

    LCD_WriteCommand(0x2c); //	开始写入显存，即要显示的颜色数据

    while ((LCD_SPI.Instance->SR & 0x0080) != RESET)
        ;     //	等待通信完成
    LCD_CS_H; // 片选拉高
}

/****************************************************************************************************************************************
 *	函 数 名:	LCD_SetDirection
 *
 *	入口参数:	direction - 要显示的方向
 *
 *	函数功能:	设置要显示的方向
 *
 *	说    明:   1. 可输入参数 Direction_H 、Direction_V 、Direction_H_Flip 、Direction_V_Flip
 *              2. 使用示例 LCD_DisplayDirection(Direction_H) ，即设置屏幕横屏显示
 *
 *****************************************************************************************************************************************/

void LCD_SetDirection(uint8_t direction)
{
    LCD.Direction = direction; // 写入全局LCD参数

    LCD_CS_L; // 片选拉低，使能IC

    if (direction == Direction_H) // 横屏显示
    {
        LCD_WriteCommand(0x36);   // 显存访问控制 指令，用于设置访问显存的方式
        LCD_WriteData_8bit(0x70); // 横屏显示
        LCD.Width = LCD_Height;   // 重新赋值长、宽
        LCD.Height = LCD_Width;
    }
    else if (direction == Direction_V)
    {
        LCD_WriteCommand(0x36);   // 显存访问控制 指令，用于设置访问显存的方式
        LCD_WriteData_8bit(0x00); // 垂直显示
        LCD.Width = LCD_Width;    // 重新赋值长、宽
        LCD.Height = LCD_Height;
    }
    else if (direction == Direction_H_Flip)
    {
        LCD_WriteCommand(0x36);   // 显存访问控制 指令，用于设置访问显存的方式
        LCD_WriteData_8bit(0xA0); // 横屏显示，并上下翻转，RGB像素格式
        LCD.Width = LCD_Height;   // 重新赋值长、宽
        LCD.Height = LCD_Width;
    }
    else if (direction == Direction_V_Flip)
    {
        LCD_WriteCommand(0x36);   // 显存访问控制 指令，用于设置访问显存的方式
        LCD_WriteData_8bit(0xC0); // 垂直显示 ，并上下翻转，RGB像素格式
        LCD.Width = LCD_Width;    // 重新赋值长、宽
        LCD.Height = LCD_Height;
    }
    while ((LCD_SPI.Instance->SR & 0x0080) != RESET)
        ;     //	等待通信完成
    LCD_CS_H; // 片选拉高
}

/****************************************************************************************************************************************
 *	函 数 名:	LCD_Clear
 *
 *	函数功能:	清屏函数，将LCD清除为 LCD.BackColor 的颜色
 *
 *	说    明:	先用 LCD_SetBackColor() 设置要清除的背景色，再调用该函数清屏即可
 *
 *****************************************************************************************************************************************/

void LCD_Clear(void)
{
    uint32_t i;

    LCD_SetAddress(0, 0, LCD.Width - 1, LCD.Height - 1); // 设置坐标

    LCD_SPI.Instance->CR1 &= 0xFFBF; // 关闭SPI
    LCD_SPI.Instance->CR1 |= 0x0800; // 切换成16位数据格式
    LCD_SPI.Instance->CR1 |= 0x0040; // 使能SPI

    LCD_CS_L; // 片选拉低，使能IC

    for (i = 0; i < LCD.Width * LCD.Height; i++)
    {

        LCD_SPI.Instance->DR = LCD.BackColor;
        while ((LCD_SPI.Instance->SR & 0x0002) == 0)
            ; // 等待发送缓冲区清空
    }
    while ((LCD_SPI.Instance->SR & 0x0080) != RESET)
        ;     //	等待通信完成
    LCD_CS_H; // 片选拉高

    LCD_SPI.Instance->CR1 &= 0xFFBF; // 关闭SPI
    LCD_SPI.Instance->CR1 &= 0xF7FF; // 切换成8位数据格式
    LCD_SPI.Instance->CR1 |= 0x0040; // 使能SPI
}

/****************************************************************************************************************************************
 *	函 数 名:	LCD_ClearRect
 *
 *	入口参数:	x - 起始水平坐标
 *					y - 起始垂直坐标
 *					width  - 要清除区域的横向长度
 *					height - 要清除区域的纵向宽度
 *
 *	函数功能:	局部清屏函数，将指定位置对应的区域清除为 LCD.BackColor 的颜色
 *
 *	说    明:	1. 先用 LCD_SetBackColor() 设置要清除的背景色，再调用该函数清屏即可
 *				   2. 使用示例 LCD_ClearRect( 10, 10, 100, 50) ，清除坐标(10,10)开始的长100宽50的区域
 *
 *****************************************************************************************************************************************/

void LCD_ClearRect(uint16_t x, uint16_t y, uint16_t width, uint16_t height)
{
    uint16_t i;

    LCD_SetAddress(x, y, x + width - 1, y + height - 1); // 设置坐标

    LCD_SPI.Instance->CR1 &= 0xFFBF; // 关闭SPI
    LCD_SPI.Instance->CR1 |= 0x0800; // 切换成16位数据格式
    LCD_SPI.Instance->CR1 |= 0x0040; // 使能SPI

    LCD_CS_L; // 片选拉低，使能IC

    for (i = 0; i < width * height; i++)
    {

        LCD_SPI.Instance->DR = LCD.BackColor;
        while ((LCD_SPI.Instance->SR & 0x0002) == 0)
            ; // 等待发送缓冲区清空
    }
    while ((LCD_SPI.Instance->SR & 0x0080) != RESET)
        ;     //	等待通信完成
    LCD_CS_H; // 片选拉高

    LCD_SPI.Instance->CR1 &= 0xFFBF; // 关闭SPI
    LCD_SPI.Instance->CR1 &= 0xF7FF; // 切换成8位数据格式
    LCD_SPI.Instance->CR1 |= 0x0040; // 使能SPI
}

/**
 * @brief       画点
 * @param       x,y: 坐标
 * @param       color: 点的颜色(32位颜色,方便兼容LTDC)
 * @retval      无
 */
void lcd_draw_point(uint16_t x, uint16_t y, uint16_t color)
{
    LCD_SetAddress(x, y, x, y); //	设置坐标

    LCD_CS_L; // 片选拉低，使能IC

    LCD_WriteData_16bit(color);

    while ((LCD_SPI.Instance->SR & 0x0080) != RESET)
        ;     //	等待通信完成
    LCD_CS_H; // 片选拉高
}

/**
 * @brief       在指定位置显示一个字符
 * @param       x,y   : 坐标
 * @param       chr   : 要显示的字符:" "--->"~"
 * @param       size  : 字体大小 12/16/24/32
 * @param       mode  : 叠加方式(1); 非叠加方式(0);
 * @param       color : 字符的颜色;
 * @retval      无
 */
void lcd_show_char(uint16_t x, uint16_t y, char chr, uint8_t size, uint8_t mode, uint16_t color)
{
    uint8_t temp, t1, t;
    uint16_t y0 = y;
    uint8_t csize = 0;
    uint8_t *pfont = 0;

    csize = (size / 8 + ((size % 8) ? 1 : 0)) * (size / 2); /* 得到字体一个字符对应点阵集所占的字节数 */
    chr = chr - ' ';                                        /* 得到偏移后的值（ASCII字库是从空格开始取模，所以-' '就是对应字符的字库） */

    switch (size)
    {
    case 12:
        pfont = (uint8_t *)asc2_1206[chr]; /* 调用1206字体 */
        break;

    case 16:
        pfont = (uint8_t *)asc2_1608[chr]; /* 调用1608字体 */
        break;

    case 24:
        pfont = (uint8_t *)asc2_2412[chr]; /* 调用2412字体 */
        break;

    case 32:
        pfont = (uint8_t *)asc2_3216[chr]; /* 调用3216字体 */
        break;

    default:
        return;
    }

    for (t = 0; t < csize; t++)
    {
        temp = pfont[t]; /* 获取字符的点阵数据 */

        for (t1 = 0; t1 < 8; t1++) /* 一个字节8个点 */
        {
            if (temp & 0x80) /* 有效点,需要显示 */
            {
                lcd_draw_point(x, y, color); /* 画点出来,要显示这个点 */
            }
            else if (mode == 0) /* 无效点,不显示 */
            {
                lcd_draw_point(x, y, LCD.BackColor); /* 画背景色,相当于这个点不显示(注意背景色由全局变量控制) */
            }

            temp <<= 1; /* 移位, 以便获取下一个位的状态 */
            y++;

            if (y >= LCD.Height)
                return; /* 超区域了 */

            if ((y - y0) == size) /* 显示完一列了? */
            {
                y = y0; /* y坐标复位 */
                x++;    /* x坐标递增 */

                if (x >= LCD.Width)
                    return; /* x坐标超区域了 */

                break;
            }
        }
    }
}

/**
 * @brief       平方函数, m^n
 * @param       m: 底数
 * @param       n: 指数
 * @retval      m的n次方
 */
static uint32_t lcd_pow(uint8_t m, uint8_t n)
{
    uint32_t result = 1;

    while (n--)
        result *= m;

    return result;
}

/**
 * @brief       扩展显示len个数字(高位是0也显示)
 * @param       x,y   : 起始坐标
 * @param       num   : 数值(0 ~ 2^32)
 * @param       len   : 显示数字的位数
 * @param       size  : 选择字体 12/16/24/32
 * @param       mode  : 显示模式
 *              [7]:0,不填充;1,填充0.
 *              [6:1]:保留
 *              [0]:0,非叠加显示;1,叠加显示.
 * @param       color : 数字的颜色;
 * @retval      无
 */
void LCD_SHOW_XNUM(uint16_t x, uint16_t y, uint32_t num, uint8_t len, uint8_t size, uint8_t mode, uint16_t color)
{
    uint8_t t, temp;
    uint8_t enshow = 0;

    for (t = 0; t < len; t++) /* 按总显示位数循环 */
    {
        temp = (num / lcd_pow(10, len - t - 1)) % 10; /* 获取对应位的数字 */

        if (enshow == 0 && t < (len - 1)) /* 没有使能显示,且还有位要显示 */
        {
            if (temp == 0)
            {
                if (mode & 0X80) /* 高位需要填充0 */
                {
                    lcd_show_char(x + (size / 2) * t, y, '0', size, mode & 0X01, color); /* 用0占位 */
                }
                else
                {
                    lcd_show_char(x + (size / 2) * t, y, ' ', size, mode & 0X01, color); /* 用空格占位 */
                }

                continue;
            }
            else
            {
                enshow = 1; /* 使能显示 */
            }
        }

        lcd_show_char(x + (size / 2) * t, y, temp + '0', size, mode & 0X01, color);
    }
}

/**
 * @brief       显示字符串
 * @param       x,y         : 起始坐标
 * @param       width,height: 区域大小
 * @param       size        : 选择字体 12/16/24/32
 * @param       p           : 字符串首地址
 * @param       color       : 字符串的颜色;
 * @retval      无
 */
void LCD_SHOW_STRING(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t size, char *p, uint16_t color)
{
    uint8_t x0 = x;
    width += x;
    height += y;

    while ((*p <= '~') && (*p >= ' ')) /* 判断是不是非法字符! */
    {
        if (x >= width)
        {
            x = x0;
            y += size;
        }

        if (y >= height)
            break; /* 退出 */

        lcd_show_char(x, y, *p, size, 0, color);
        x += size / 2;
        p++;
    }
}

void LCD_FLUSH(void)
{
    uint32_t i;
    LCD_SetAddress(0, 0, LCD_Width - 1, LCD_Height - 1); // 设置坐标

    LCD_SPI.Instance->CR1 &= 0xFFBF; // 关闭SPI
    LCD_SPI.Instance->CR1 |= 0x0800; // 切换成16位数据格式
    LCD_SPI.Instance->CR1 |= 0x0040; // 使能SPI

    LCD_CS_L; // 片选拉低，使能IC

    for (i = 0; i < LCD_Width * LCD_Height; i++)
    {

        LCD_SPI.Instance->DR = LCD_BUFF[i];
        while ((LCD_SPI.Instance->SR & 0x0002) == 0)
            ; // 等待发送缓冲区清空
    }
    while ((LCD_SPI.Instance->SR & 0x0080) != RESET)
        ;     //	等待通信完成
    LCD_CS_H; // 片选拉高

    LCD_SPI.Instance->CR1 &= 0xFFBF; // 关闭SPI
    LCD_SPI.Instance->CR1 &= 0xF7FF; // 切换成8位数据格式
    LCD_SPI.Instance->CR1 |= 0x0040; // 使能SPI
}
